# ---------- 文件信息 ----------#
# PRECISION.py
# 本文件所实现的方案为文章"Efficient Measurement on
# Programmable Switches Using Probabilistic Recirculation"
# 中所描述的PRECISION.
# 作者：赵宗义，xinshengzzy@foxmail.com, 2021.01.13
# -------------------- #
from simulators.BaseSimulator import BaseSimulator
from simulators.Buckets import FlowRecord
from 标准库 import 累积序列
import mytools
import random
import 文件工具
import math


class PRECISION(BaseSimulator):
    def __init__(self, 内存=1.0, 导出周期=-1, 协议="ipv4"):
        super(PRECISION, self).__init__()
        self.协议 = 协议
        self.内存 = 内存
        self.导出周期 = 导出周期
        random.seed(0)
        self.计算表长()
        self.哈希表 = []
        self.初始化内存()
        self.tmp = [0] * 1024

    def 初始化内存(self):
        self.哈希表 = []
        for i in range(self.表长1 + self.表长2 + self.表长3 + self.表长4):
            self.哈希表.append(FlowRecord())

    def 计算表长(self):
        if self.周期模式 == self.导出模式 and "ipv4" == self.协议:
            tmp = int(self.内存*1024.0*1024.0/68.0)
        elif self.周期模式 == self.导出模式 and "ipv6" == self.协议:
            tmp = int(self.内存*1024.0*1024.0/164.0)
        elif self.超时模式 == self.导出模式:
            tmp = int(self.内存*1024.0*1024.0/76.0)
        else:
            raise SystemExit("计算表长失败!")
        self.表长1 = tmp
        self.表长2 = tmp
        self.表长3 = tmp
        self.表长4 = tmp

    def 计算导出周期(self, 带宽消耗):
        # 此处带宽消耗的单位是bps
        if "ipv4" == self.协议:
            流记录宽度 = 17.0*8  # 这里默认的流标识符是IPv4的五元组, 长度单位为"位"
        elif "ipv6" == self.协议:
            流记录宽度 = 41.0*8
        self.导出周期 = (流记录宽度*(self.表长1+self.表长2+self.表长3+self.表长4))/带宽消耗

    def 计算索引(self, 标识符):
        偏移量 = 累积序列([0, self.表长1, self.表长2, self.表长3])
        索引1 = mytools.hash1(标识符.encode('utf-8')) % self.表长1 + 偏移量[0]
        索引2 = mytools.hash2(标识符.encode('utf-8')) % self.表长2 + 偏移量[1]
        索引3 = mytools.hash3(标识符.encode('utf-8')) % self.表长3 + 偏移量[2]
        索引4 = mytools.hash4(标识符.encode('utf-8')) % self.表长4 + 偏移量[3]
        return (索引1, 索引2, 索引3, 索引4)

    def 周期式数据包处理(self, 数据包):
        标识符 = 数据包.提取流标识符()
        if self.统计模式 == self.字节模式:
            增量 = 数据包.length
        elif self.统计模式 == self.包模式:
            增量 = 1
        else:
            raise SystemExit("无法识别统计模式:", self.统计模式)
        索引序列 = self.计算索引(标识符)
        最小计数值 = -1
        最小索引 = -1
        for 索引 in 索引序列:
            if self.哈希表[索引].empty():
                self.哈希表[索引].init(标识符, 增量)
                return
            elif self.哈希表[索引].match(标识符):
                self.哈希表[索引].increCount(增量)
                return
            elif 最小计数值 < 0 or self.哈希表[索引].count < 最小计数值:
                最小计数值 = self.哈希表[索引].count
                最小索引 = 索引

        new_value = 2**math.ceil(math.log(最小计数值, 2))
        R = random.randint(0, new_value - 1)
        if R < 增量:
            # print("最小计数值:%d, new_value:%d" % (最小计数值, new_value))
            self.哈希表[最小索引].init(标识符, new_value)
            self.升级次数 += 1
        if 最小计数值 < 1024:
            self.tmp[最小计数值] += 1

    def 超时式数据包处理(self, 标识符, 时间戳):
        索引序列 = self.计算索引(标识符)
        最小计数值 = -1
        最小索引 = -1
        for 索引 in 索引序列:
            if self.哈希表[索引].empty():
                self.哈希表[索引].init(标识符, 1, 时间戳)
                return
            elif self.哈希表[索引].flowID == 标识符:
                self.哈希表[索引].increCount(1, 时间戳)
                return
            else:
                if self.超时时间 > 0 and self.哈希表[索引].getTs() + self.超时时间 < 时间戳:
                    self.导出流记录(self.哈希表[索引].getRecord())
                    self.哈希表[索引].init(标识符, 1, 时间戳)
                    return
                elif 最小计数值 < 0 or self.哈希表[索引].count < 最小计数值:
                    最小计数值 = self.哈希表[索引].count
                    最小索引 = 索引

        new_value = 2**math.ceil(math.log(最小计数值, 2))
        R = random.randint(0, new_value - 1)
        if 0 == R:
            self.哈希表[最小索引].init(标识符, new_value)

    def 在线流长查询(self, 标识符):
        流长 = 0
        索引序列 = self.计算索引(标识符)
        for idx in 索引序列:
            if 标识符 == self.哈希表[idx].flowID:
                流长 += self.哈希表[idx].count
        return 流长

    def 下载数据平面(self):
        流集 = dict()
        for item in self.哈希表:
            if not item.empty():
                标识符, 计数值 = item.getRecord()
                if 标识符 not in 流集:
                    流集[标识符] = 0
                流集[标识符] += 计数值
        return 流集

    def 重置数据平面(self):
        for 索引 in range(self.表长1 + self.表长2 + self.表长3 + self.表长4):
            self.哈希表[索引].reset()

    def 备份(self, 文件名):
        备份 = dict()
        备份["流记录集"] = self.获取流记录集()
        备份["导出次数"] = self.导出次数
        备份["升级次数"] = self.升级次数
        文件工具.备份数据(备份, 文件名)
